#include "vsf.h"

#ifdef BCMWIFI_SPI
static void bcm_bus_spi_select(struct bcm_bus_t *bus)
{
	if (bus->port.spi.cs_port != VSFHAL_DUMMY_PORT)
	{
		vsfhal_gpio_clear(bus->port.spi.cs_port, 1 << bus->port.spi.cs_pin);
	}
	else
	{
//		vsfhal_spi_select(bus->port.index, bus->port.spi.cs_pin);
	}
}

static void bcm_bus_spi_deselect(struct bcm_bus_t *bus)
{
	if (bus->port.spi.cs_port != VSFHAL_DUMMY_PORT)
	{
		vsfhal_gpio_set(bus->port.spi.cs_port, 1 << bus->port.spi.cs_pin);
	}
	else
	{
//		vsfhal_spi_select(bus->port.index, bus->port.spi.cs_pin);
	}
}

enum bcm_bus_spi_EVT_t
{
	BCM_BUS_SPI_EVT_READY = VSFSM_EVT_USER_LOCAL + 0,
};

static void bcm_bus_spi_callback(void *param)
{
	struct bcm_bus_t *bcm_bus = (struct bcm_bus_t *)param;
	
	if (bcm_bus->bus_ops_sm != NULL)
	{
		vsfsm_post_evt_pending(bcm_bus->bus_ops_sm, BCM_BUS_SPI_EVT_READY);
	}
}

uint32_t bcm_bus_spi_fix_u32(struct bcm_bus_t *bcm_bus, uint32_t value)
{
#if defined(__BIG_ENDIAN__) && (__BIG_ENDIAN__ == 1)
#error not supported
#else
	if ((BCM_BUS_ENDIAN_LE == bcm_bus->endian) &&
		(BCM_BUS_WORDLEN_16 == bcm_bus->word_length))
	{
		value = ((value & 0x00FF00FF) << 8) | ((value & 0xFF00FF00) >> 8);
	}
#endif
	return value;
}

static vsf_err_t bcm_bus_spi_transact(struct vsfsm_pt_t *pt,
		vsfsm_evt_t evt, uint8_t rw, uint8_t func, uint32_t addr, uint16_t size,
		struct vsf_buffer_t *buffer)
{
	struct bcm_bus_t *bcm_bus = pt->user_data;
	struct bcm_bus_spi_t *p = &bcm_bus->priv.spi;
	
	bcm_bus->bus_ops_sm = pt->sm;
	vsfsm_pt_begin(pt);
	
	if ((NULL == buffer) || ((buffer->size > 0) && (NULL == buffer->buffer)))
	{
		return VSFERR_FAIL;
	}
	
	bcm_bus->buslen = size;
	if ((BCM_CMD_READ == rw) && (BCM_FUNC_BACKPLANE == func))
	{
		bcm_bus->buslen += 4;
	}
	p->bushead = bcm_bus_spi_fix_u32(bcm_bus,
			((rw & 1) << 31) | ((BCM_ACCESS_INCREMENTAL & 1) << 30) |
			((func & 0x03) << 28) | ((addr & 0x1FFFF) << 11) |
			(bcm_bus->buslen & 0x7FF));
	bcm_bus->buslen = ((bcm_bus->buslen + 3) & ~3);
	
	bcm_bus_spi_select(bcm_bus);
	vsfhal_spi_start(bcm_bus->port.index, (uint8_t *)&p->bushead, NULL, 4);
	vsfsm_pt_wfe(pt, BCM_BUS_SPI_EVT_READY);
	if (bcm_bus->buslen > buffer->size)
	{
		vsfhal_spi_start(bcm_bus->port.index,
								BCM_CMD_READ == rw ? NULL : buffer->buffer,
								BCM_CMD_READ == rw ? buffer->buffer : NULL,
								buffer->size);
		vsfsm_pt_wfe(pt, BCM_BUS_SPI_EVT_READY);
		vsfhal_spi_start(bcm_bus->port.index, NULL, NULL,
								bcm_bus->buslen - buffer->size);
		vsfsm_pt_wfe(pt, BCM_BUS_SPI_EVT_READY);
	}
	else
	{
		vsfhal_spi_start(bcm_bus->port.index,
								BCM_CMD_READ == rw ? NULL : buffer->buffer,
								BCM_CMD_READ == rw ? buffer->buffer : NULL,
								bcm_bus->buslen);
		vsfsm_pt_wfe(pt, BCM_BUS_SPI_EVT_READY);
	}
	bcm_bus_spi_deselect(bcm_bus);
	
	buffer->buffer +=
		((BCM_CMD_READ == rw) && (BCM_FUNC_BACKPLANE == func)) ? 4 : 0;
	vsfsm_pt_end(pt);
	bcm_bus->bus_ops_sm = NULL;
	return VSFERR_NONE;
}

// low-level interrupt will be disabled when triggered
// sdpcm will call enable_int to enable the interrupt
// no nothing if fall-edge interrupt
static void bcm_bus_spi_enable_int(struct bcm_bus_t *bus)
{
	if (bus->port.spi.eint != VSFHAL_DUMMY_PORT)
	{
		vsfhal_eint_enable(bus->port.spi.eint);
	}
}

// no nothing for low-level interrupt
// check GPIO for fall-edge interrupt
// always return true for poll(no interrupt)
static bool bcm_bus_spi_is_int(struct bcm_bus_t *bus)
{
	if (bus->port.spi.eint != VSFHAL_DUMMY_PORT)
	{
		return !vsfhal_gpio_get(bus->port.spi.eint_port,
										1 << bus->port.spi.eint_pin);
	}
	else
	{
		return true;
	}
}

static void bcm_bus_spi_fini_int(struct bcm_bus_t *bus)
{
	if (bus->port.spi.eint != VSFHAL_DUMMY_PORT)
	{
		vsfhal_eint_disable(bus->port.spi.eint);
	}
}

static void bcm_bus_spi_init_int(struct bcm_bus_t *bus,
									void (*callback)(void *param), void *param)
{
	if (bus->port.spi.eint != VSFHAL_DUMMY_PORT)
	{
		vsfhal_gpio_init(bus->port.spi.eint_port);
		vsfhal_gpio_config(bus->port.spi.eint_port, bus->port.spi.eint_pin,
				VSFHAL_GPIO_INPUT);
		vsfhal_eint_init(bus->port.spi.eint);
		vsfhal_eint_config(bus->port.spi.eint, VSFHAL_EINT_ONFALL, 0, callback,
								param);
		vsfhal_eint_enable(bus->port.spi.eint);
	}
}

static vsf_err_t bcm_bus_spi_f2_avail(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
										uint16_t *size)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;

	vsfsm_pt_begin(pt);

	if (!bcm_bus->receiving)
	{
		// read interrupt flag
		bcm_bus->f2_pt.sm = pt->sm;
		bcm_bus->f2_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->f2_pt, evt, BCM_SPIREG_INTF, 2,
								(uint8_t *)&bcm_bus->intf);
		if (err != 0) return err;
		
		if (bcm_bus->intf)
		{
			// clear interrupt flag
			bcm_bus->f2_pt.state = 0;
			vsfsm_pt_entry(pt);
			err = bcm_bus_write_reg(&bcm_bus->f2_pt, evt, BCM_SPIREG_INTF, 2,
									bcm_bus->intf);
			if (err != 0) return err;
		}
		
		if (bcm_bus->intf & BCM_INTF_F2_PACKET_AVAIL)
		{
			bcm_bus->receiving = true;
		}
	}

	if (bcm_bus->receiving)
	{
		// read status
		bcm_bus->f2_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->f2_pt, evt, BCM_SPIREG_STATUS, 4,
								(uint8_t *)&bcm_bus->status);
		if (err != 0) return err;
	}

	vsfsm_pt_end(pt);

	*size = (bcm_bus->status >> 9) & 0x7FF;
	if (!*size)
	{
		bcm_bus->receiving = false;
	}
	return VSFERR_NONE;
}

static vsf_err_t bcm_bus_spi_f2_read(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
									uint16_t size, struct vsf_buffer_t *buffer)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;

	vsfsm_pt_begin(pt);

	bcm_bus->f2_pt.sm = pt->sm;
	bcm_bus->f2_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_transact(&bcm_bus->f2_pt, evt, BCM_CMD_READ, BCM_FUNC_F2,
							0, size, buffer);
	if (err != 0) return err;

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static vsf_err_t bcm_bus_spi_waitf2(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	vsf_err_t err;
	struct bcm_bus_t *bcm_bus = pt->user_data;

	vsfsm_pt_begin(pt);

	while (1)
	{
		bcm_bus->port_init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, BCM_SPIREG_STATUS,
								4, (uint8_t *)&bcm_bus->tmpreg);
		if (err != 0) return err;

		if (bcm_bus->tmpreg & BCM_STATUS_F2_RXRDY)
		{
			break;
		}
		vsfsm_pt_delay(pt, 5);
	}

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static uint32_t bcm_bus_spi_f2size(uint32_t f2size)
{
	return f2size;
}

static vsf_err_t bcm_bus_spi_init(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct bcm_bus_t *bcm_bus = pt->user_data;
	struct bcm_bus_spi_t *p = &bcm_bus->priv.spi;
	vsf_err_t err;
	
	vsfsm_pt_begin(pt);
	
	p->retry = 0;
	
	vsfhal_spi_init(bcm_bus->port.index);
	vsfhal_spi_config(bcm_bus->port.index, bcm_bus->port.freq_khz,
			VSFHAL_SPI_MODE0 | VSFHAL_SPI_MASTER);
	vsfhal_spi_config_callback(bcm_bus->port.index, 0, bcm_bus,
			bcm_bus_spi_callback);
	vsfhal_gpio_init(bcm_bus->port.spi.cs_port);
	// cs is configured to PP mode to get faster speed
	vsfhal_gpio_config(bcm_bus->port.spi.cs_port, bcm_bus->port.spi.cs_pin,
			VSFHAL_GPIO_OUTPP);
	bcm_bus_spi_deselect(bcm_bus);
	
retry:
	vsfhal_gpio_clear(bcm_bus->port.rst_port, 1 << bcm_bus->port.rst_pin);
	vsfsm_pt_delay(pt, 50);
	vsfhal_gpio_set(bcm_bus->port.rst_port, 1 << bcm_bus->port.rst_pin);
	vsfsm_pt_delay(pt, 50);
	
	bcm_bus->endian = BCM_BUS_ENDIAN_LE;
	bcm_bus->word_length = BCM_BUS_WORDLEN_16;
	// set to bigger endian, 32 bit
	bcm_bus->port_init_pt.sm = pt->sm;
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, BCM_SPIREG_CONTROL, 4,
							0x00020483);
	if (err != 0) return err;
	
	bcm_bus->endian = BCM_BUS_ENDIAN_BE;
	bcm_bus->word_length = BCM_BUS_WORDLEN_32;
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_read_reg(&bcm_bus->port_init_pt, evt, BCM_SPIREG_TEST, 4,
							(uint8_t *)&bcm_bus->tmpreg);
	if (err != 0) return err;
	if (bcm_bus->tmpreg != 0xFEEDBEAD)
	{
		if (++p->retry < 3)
		{
			goto retry;
		}
		return VSFERR_FAIL;
	}
	
	// clear error interrupt flags
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, BCM_SPIREG_INTF, 1,
		BCM_INTF_DATA_UNAVIL | BCM_INTF_COMMAND_ERROR | BCM_INTF_DATA_ERROR |
		BCM_INTF_F1_OVERFLOW);
	if (err != 0) return err;
	
	// enable interrupts
	bcm_bus->port_init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_write_reg(&bcm_bus->port_init_pt, evt, BCM_SPIREG_INTEN, 2,
		BCM_INTF_F23_FIFO_RD_UNDERFLOW | BCM_INTF_F23_FIFO_WR_OVERFLOW |
		BCM_INTF_COMMAND_ERROR | BCM_INTF_DATA_ERROR |
		BCM_INTF_F2_PACKET_AVAIL | BCM_INTF_F1_OVERFLOW);
	if (err != 0) return err;
	
	vsfsm_pt_delay(pt, 100);
	
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

const struct bcm_bus_op_t bcm_bus_spi_op =
{
	bcm_bus_spi_init, NULL, bcm_bus_spi_waitf2, bcm_bus_spi_f2size,
	bcm_bus_spi_init_int, bcm_bus_spi_fini_int,
	bcm_bus_spi_enable_int, bcm_bus_spi_is_int,
	bcm_bus_spi_f2_avail, bcm_bus_spi_f2_read, NULL,
	bcm_bus_spi_fix_u32, bcm_bus_spi_transact
};
#endif		// BCMWIFI_SPI
